UFABC - ESTI019 - Codificação de Sinais Multimídia
Neste laboratório, foram utilizadas as bibliotecas Scipy e Librosa, dentro do ambiente de desenvolvimento do Google Colab, para estudar técnicas relacionadas ao processamento digital de sinais aplicadas na análise de áudios de voz, utilizando a linguagem Python.
Dentre as atividades de processamento desenvolvidas, foi possível atingir os seguintes objetivos:
Link para o ambiente Colab utilizado no desenvolvimento desta prática:
Usando o Audacity, ou outro programa de áudio, grave arquivos com:
Adquirindo os Arquivos de áudio do repositório do GitHub:
!wget -q Entre_Leva_Catia_Falada.wav https://raw.githubusercontent.com/gabrielsouza-oss/CSM/main/labs/Entre_Leva_Catia_Falada.wav
!wget -q "Voz Declamada.ogg" https://raw.githubusercontent.com/gabrielsouza-oss/CSM/main/labs/LAB6/Voz%20Declamada.ogg
!wget -q "Voz Voz Digitos.ogg" https://raw.githubusercontent.com/gabrielsouza-oss/CSM/main/labs/LAB6/Voz%20Digitos.ogg
!wget -q "Voz Falada.ogg" https://raw.githubusercontent.com/gabrielsouza-oss/CSM/main/labs/LAB6/Voz%20Falada.ogg
!wget -q "Voz Lida.ogg" https://raw.githubusercontent.com/gabrielsouza-oss/CSM/main/labs/LAB6/Voz%20Lida.ogg
Importando todas as Bibliotecas utilzadas durante esta prática de laboratório, sendo as principais:
import numpy as np
import matplotlib.pyplot as plt
import librosa
import librosa.display
import IPython.display
from scipy import signal
from matplotlib.ticker import (MultipleLocator, FormatStrFormatter, AutoMinorLocator)
from scipy.signal import find_peaks
import pandas as pd
Função utilizada para ler os arquivos de áudio com a biblioteca Librosa, além de envolver os dados do arquivo de áudio em um envoltório HTML, para visualização no ambiente do Google Colab e plotar a forma de onda completa dos áudios lidos (Amplitude x Tempo).
def ler_audio(audio1):
v1 , sr1 = librosa.load(audio1)
print(type(v1), type(sr1))
print(v1.shape, sr1)
# Player será aberto! AGUARDE até abrir!
display(IPython.display.Audio(data=v1, rate=sr1))
plt.figure()
fig, ax = plt.subplots(figsize=(15, 5))
librosa.display.waveplot(v1, sr=sr1)
plt.title(audio1)
return v1,sr1,audio1
Abaixo, podemos visualizar os áudios originais gravados pelos intgrantes do grupo, lidos no ambiente do Google Colab:
digitos = ler_audio("Voz Digitos.ogg")
/usr/local/lib/python3.7/dist-packages/librosa/core/audio.py:165: UserWarning: PySoundFile failed. Trying audioread instead.
warnings.warn("PySoundFile failed. Trying audioread instead.")
<class 'numpy.ndarray'> <class 'int'> (327222,) 22050
<Figure size 432x288 with 0 Axes>
falado = ler_audio("Voz Falada.ogg")
/usr/local/lib/python3.7/dist-packages/librosa/core/audio.py:165: UserWarning: PySoundFile failed. Trying audioread instead.
warnings.warn("PySoundFile failed. Trying audioread instead.")
<class 'numpy.ndarray'> <class 'int'> (332514,) 22050
<Figure size 432x288 with 0 Axes>
declamado = ler_audio("Voz Declamada.ogg")
/usr/local/lib/python3.7/dist-packages/librosa/core/audio.py:165: UserWarning: PySoundFile failed. Trying audioread instead.
warnings.warn("PySoundFile failed. Trying audioread instead.")
<class 'numpy.ndarray'> <class 'int'> (274743,) 22050
<Figure size 432x288 with 0 Axes>
lida = ler_audio("Voz Lida.ogg")
/usr/local/lib/python3.7/dist-packages/librosa/core/audio.py:165: UserWarning: PySoundFile failed. Trying audioread instead.
warnings.warn("PySoundFile failed. Trying audioread instead.")
<class 'numpy.ndarray'> <class 'int'> (386757,) 22050
<Figure size 432x288 with 0 Axes>
Nesta parte do laboratório, obtivemos alguns parâmetros relativos aos áudios originais completos gravados pelos integrantes, tais como:
Com estes parâmetros definidos, foi possível obter um plot da janela de Hamming definida para cada um dos áudios dos integrantes do Grupo e partindo desta janela, pudemos estimar a Energia de tempo curto dos arquivos de áudio.
Abaixo, temos a função desenvolvida para realizar os procedimentos acima descritos:
def parametros_audio(v1,sr1,audio1):
#Obtendo os Parâmetros do áudio
display(IPython.display.HTML("<h2> 2.1 Parâmetros do Áudio"))
print('Frequência de Amostragem', sr1)
fa = sr1
print(fa)
Ts = 0.04 # Tempo de duração do segmento em segundos
Nj = int(Ts*fa) # Número de pontos da Janela
print('Tamanho do Segmento', Nj)
Nseg = int(len(v1)/Nj)
Nover = int(Nj*0.5)
print('Número de Segmentos no Arquivo', Nseg, '. Pontos Soprepostos', Nover )
display(IPython.display.HTML("<h2> 2.2 Obtendo uma Janela de Hamming </h3>"))
#Janela de Haming
hm = signal.get_window('hamming', Nj)
plt.plot(hm); plt.title('Janela de Hamming')
plt.show()
display(IPython.display.HTML("<h2> 3. Energia de Tempo Curto </h2>"))
#Energia de Tempo Curto
Nover = int(Nj*0.5)
Nseg = int(len(v1)/Nj)
E = []
for l in range(1, Nseg):
xjan = v1[(l-1)*Nj+Nover:l*Nj+Nover]*hm
x2 = list(np.array(xjan**2))
aux = sum(x2)/Nj
E.append(aux)
E = 10*np.log10(E)
Emin = np.min(E) # calcula nível de ruído de fundo
plt.figure
fig, ax = plt.subplots(figsize=(15, 3))
plt.plot(E - Emin)
plt.title('Energia da ' + audio1)
plt.ylabel('Energia[db]'); plt.xlabel('Segmento')
plt.show()
return Nj,Nseg,Nover
Abaixo, podemos visualizar a saída obtida da função, para cada um dos áudios originais gravados pelos integrantes do grupo:
Podemos observar que os áudios relativos às vozes "Declamada" e "Lida", gravadas pelo integrante Gabriel, apresentaram os maiores picos de Energia de Tempo curto, alcançando os 80dB.
Observamos também que o áudio relativo à voz "Falada", gravada pelo integrante João, alcançou apenas, cerca de 45dB de Energia de Tempo curto, em seu pico, sendo a menor dentre todas as vozes.
parametros_digitos = parametros_audio(digitos[0],digitos[1],digitos[2])
Frequência de Amostragem 22050 22050 Tamanho do Segmento 882 Número de Segmentos no Arquivo 371 . Pontos Soprepostos 441
parametros_falados = parametros_audio(falado[0],falado[1],falado[2])
Frequência de Amostragem 22050 22050 Tamanho do Segmento 882 Número de Segmentos no Arquivo 377 . Pontos Soprepostos 441
parametros_declamado = parametros_audio(declamado[0],declamado[1],declamado[2])
Frequência de Amostragem 22050 22050 Tamanho do Segmento 882 Número de Segmentos no Arquivo 311 . Pontos Soprepostos 441
parametros_lida = parametros_audio(lida[0],lida[1],lida[2])
Frequência de Amostragem 22050 22050 Tamanho do Segmento 882 Número de Segmentos no Arquivo 438 . Pontos Soprepostos 441
Nesta parte do laboratório, pudemos visualizar os espectrogramas dos áudios gravados pelos integrantes, mostrando a potência, em escala de decibéis (dB), bem como o espectrograma de frequência para cada um dos áudios originais.
Para converter a amplitude para Decibéis, foi utilizada a função librosa.amplitude_to_db(), tomando como referência o valor máximo do espectro.
Para plotar o gráfico, utilizamos a função librosa.display.specshow()
Abxaixo, temos a Função desenvolvida para plotar os espectrogramas (em decibéis), dos arquivos de áudio do grupo, utilizando as bilbioteca Librosa e MatplotLib para visualização do espectro
def visualizao_especto(v1,path):
plt.figure(figsize=(12, 8))
D = librosa.amplitude_to_db(np.abs(librosa.stft(v1)), ref=np.max)
fig, ax = plt.subplots(figsize=(15, 10))
librosa.display.specshow(D, x_axis='time',y_axis='linear')
plt.colorbar(format='%+2.0f dB')
plt.title('Potência e Espectrograma Linear na Frequência '+ path)
plt.show()
Abaixo, podemos visualizar a saída obtida da função acima, para cada um dos áudios originais gravados pelos integrantes do grupo:
visualizao_especto(digitos[0],digitos[2])
<Figure size 864x576 with 0 Axes>
visualizao_especto(falado[0],falado[2])
<Figure size 864x576 with 0 Axes>
visualizao_especto(declamado[0],declamado[2])
<Figure size 864x576 with 0 Axes>
visualizao_especto(lida[0],lida[2])
<Figure size 864x576 with 0 Axes>
Nesta parte do laboratório, isolamos alguns trechos dos áudios dos integrantes que possuem vogais, em seguida, plotamos a autocorrelação de cada um dos segmentos das vogais extraídas e utilizamos a função scipy.signal.find_peaks(), para econtrar os picos nos gráficos de autocorrelação.
A partir destes picos, foi calculado o intervalo de tempo entre os dois primeiros picos do gráfico de autocorrelação, para determinarmos o período fundamental ($T_0$) de cada um dos áudios, bem como a frequência fundamental ($f_0$).
Abaixo, temos a função desenvolvida para extrair o segmento do áudio orignal e a partir deste segmento plotar sua autocorrelação e encontrar os picos neste plot para cálculo dos valores de ($T_0$) e ($f_0$). Seguindo as seguintes relações:
O Período de Pitch ($T_{0}$) será o intervalo entre picos sucessivos.
A Frequência Fundamental ($f_0$) será o inverso do periódo de Pitch
def extrair_pitch(v1,sr1, intervalo1, intervalo2,nome):
#Segmento de áudio extraido
v1En = v1[intervalo1:intervalo2]
display(IPython.display.Audio(data=v1En, rate=sr1))
#Plotando a autocorrelação
acEn = librosa.autocorrelate(v1En, max_size= sr1/32)
fig, ax = plt.subplots(figsize=(15, 5))
ax.xaxis.set_major_locator(MultipleLocator(1))
y1 = acEn[1:400]
x1 = range(len(y1))
xx = [i*1000/sr1 for i in x1]
plt.grid(True)
ax.plot(xx,y1)
#Econtrando os picos do áudio extraido
picos, _ = find_peaks(y1)
for i in range(0,len(picos)):
plt.plot(xx[picos[i]], y1[picos[i]], "xr")
T0 = round(xx[picos[1]] - xx[picos[0]],3)
f0 = round(1/(T0*10**-3),3)
#Plotando os resultados
plt.title('Auto-correlação - '+nome)
plt.xlabel('tempo em [ms]')
plt.show()
print("Período de Pitch (T0) = "+str(T0)+" [ms]\nFrequência Fundamental (f0) = "+str(f0)+" [Hz]")
return v1En, T0
Abaixo, podemos visualizar a saída obtida da função acima, para cada um dos áudios originais gravados pelos integrantes do grupo:
pitch1 = extrair_pitch(digitos[0],digitos[1],60000,64000,digitos[2])
Período de Pitch (T0) = 2.812 [ms] Frequência Fundamental (f0) = 355.619 [Hz]
pitch2 = extrair_pitch(falado[0],falado[1],54000,56000,falado[2])
Período de Pitch (T0) = 1.859 [ms] Frequência Fundamental (f0) = 537.924 [Hz]
pitch3 = extrair_pitch(declamado[0],declamado[1],68000,71000,declamado[2])
Período de Pitch (T0) = 2.132 [ms] Frequência Fundamental (f0) = 469.043 [Hz]
pitch4 = extrair_pitch(lida[0],lida[1],47000,50000,lida[2])
Período de Pitch (T0) = 3.81 [ms] Frequência Fundamental (f0) = 262.467 [Hz]
Nesta parte do laboratório, determinamos as frequência formantes ($f_1, f_2, f_3, f_4$) dos trechos das vogais, extraídos no item 5.1, para cada um dos áudios dos integrantes do grupos.
Para isso, obtivemos os 4 primeiros picos do espectro de frequência dos sinais extraídos, em escala decibél.
O código utilizado e o resultado obtido, podem ser vistos abaixo:
def formantes(v1En,fa,audio1,d):
# AUDIO do trecho extraido anteriormente
f, Pxx_spec = signal.periodogram(v1En, fa, 'flattop', scaling='spectrum')
lf = len(f)
fig, AX = plt.subplots(figsize=(15, 5))
AX.xaxis.set_major_locator(MultipleLocator(100))
x_var = f[:int(lf/4)]
y_var = 10*np.log10(np.sqrt(Pxx_spec[:int(lf/4)]))
AX.plot(x_var, y_var)
plt.xlabel('Frequência [Hz]')
plt.ylabel('Log-spectro [dB]')
plt.title('Espectro do fonema extraído de '+audio1)
plt.grid(True)
#Econtrando os picos do áudio extraido
picos, _ = find_peaks(y_var,distance=d)
for i in range(0,4):
plt.plot(x_var[picos[i]], y_var[picos[i]], "xr")
print("Formante f"+str(i+1)+" = "+str(picos[i])+" [Hz]")
plt.show()
return picos[0:4]
Determine as formantes das vogais dos arquivos que vocês calcularam os espectrogramas
formantes_digitos = formantes(pitch1[0],digitos[1],digitos[2],8)
Formante f1 = 2 [Hz] Formante f2 = 23 [Hz] Formante f3 = 41 [Hz] Formante f4 = 63 [Hz]
formantes_falado = formantes(pitch2[0],falado[1],falado[2],8)
Formante f1 = 1 [Hz] Formante f2 = 10 [Hz] Formante f3 = 21 [Hz] Formante f4 = 31 [Hz]
formantes_declamado = formantes(pitch3[0],declamado[1],declamado[2],15)
Formante f1 = 1 [Hz] Formante f2 = 18 [Hz] Formante f3 = 37 [Hz] Formante f4 = 55 [Hz]
formantes_lida = formantes(pitch4[0],lida[1],lida[2],5)
Formante f1 = 8 [Hz] Formante f2 = 16 [Hz] Formante f3 = 23 [Hz] Formante f4 = 33 [Hz]
Nesta parte do Laboratório, isolamos algumas consoantes plosivas ("p" e "b"), econtradas em pequenos fragmentos dos áudios originais.
A função extrair_trecho(audio1,i1,i2), recebe como parâmetros o caminho para o áudio a ser lido, o intevalo de offset e duração utilizados na leitura do arquivo de áudio indicado.
def extrair_trecho(audio1,i1,i2):
#Segmento de áudio extraido
v1 , sr1 = librosa.load(audio1,offset=i1,duration=i2)
# Player será aberto! AGUARDE até abrir!
display(IPython.display.Audio(data=v1, rate=sr1))
Abaixo, podemos visualizar a saída obtida da função acima, para cada um dos áudios originais gravados pelos integrantes do grupo:
Neste aúdio, não temos consoantes plosivas, visto que o integrante realizou uma contagem numérica de 1 à 10.
Aqui temos a consoante plosiva em "complicada", próximo aos 2 segundos de duração do áudio do integrante João
extrair_trecho(falado[2],2.2,0.1)
/usr/local/lib/python3.7/dist-packages/librosa/core/audio.py:165: UserWarning: PySoundFile failed. Trying audioread instead.
warnings.warn("PySoundFile failed. Trying audioread instead.")
Aqui temos a consoante plosiva em "obra", próximo aos 2 segundos de duração do áudio do integrante Gabriel
extrair_trecho(declamado[2],1.9,0.1)
/usr/local/lib/python3.7/dist-packages/librosa/core/audio.py:165: UserWarning: PySoundFile failed. Trying audioread instead.
warnings.warn("PySoundFile failed. Trying audioread instead.")
Aqui temos a consoante plosiva em "perguntaram", próximo aos 2 segundos de duração do áudio do integrante Gabriel
extrair_trecho(lida[2],2.0,0.07)
/usr/local/lib/python3.7/dist-packages/librosa/core/audio.py:165: UserWarning: PySoundFile failed. Trying audioread instead.
warnings.warn("PySoundFile failed. Trying audioread instead.")
Neste laboratório, através da manipulação de dados de áudio, foi possível aplicar técnicas de processamento de sinais para extração e análise de componentes em arquivos de áudio/voz.
Através das bibliotecas scipy e librosa, foi possível realizar processos de ingestão e transformação dos dados para a representação algébrica, bem como a aplicação de técnicas de análise estatística das séries.
A partir dos sinais processados, foram extraídas componentes como:
As energias dos sinais (em janelas de tempo curto)
Espectrogramas (ou seja, a representação STFT do sinal)
Determinação do Pitch da Frequência Fundamental e das Formantes de Algumas Vogais
Determinação de fonemas surdos, sonoros, consoantes gerais e plosivos
Além disso, através da aplicação das técnicas de análise, foi possível avaliar diversas características intrínsecas do sinal, sendo elas: Atenuações do sinal a partir do cálculo das energias, aplicação de janelamento para redução de ondulação, de modo a entregar resultados mais precisos na obtenção do espectro de frequência dos sinais e análise da a evolução espectral de um sinal de voz.
Além das técnicas em si, foi possível aplicar boas práticas para otimização do processo de processamento dos sinais de voz e avaliação de resultados analíticos, tais como identificação de fatores atenuações, formantes (a partir de picos de espectro) e determinação de consoantes e plosivos nos sinais caracterizados.